package fr.maistre.SystemeMarché.Implementation;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import fr.maistre.SystemeMarché.Interface.MarketEvent;
import fr.maistre.SystemeMarché.Interface.MarketEventManager;
import fr.maistre.SystemeMarché.Interface.Marketable;

public class SimpleMarket implements MarketEventManager, Serializable {

	private final HashMap<String, ReferenceMarket> references = new HashMap<String, ReferenceMarket>();
	
	// tout ce qui fait les caracteristique du marché
	private final String nom;
	private final double POPULATION;
	private double SALAIRES;
	private final double IMPOTS;
	private double CHOMAGE;
	private final double EPARGNE;
	private double TAUX_INTERETS;
	private final double PRIX_TERRAIN;
	private int NombreMarketable;
	private double INFRASTRUCTURES = 0;
	private double INVESTISSEMENTS = 0;
	private double CONSOMMATION = 0;
	private double PIB = 0;
	
	// Notre marché gere les evenements
	private final List<MarketEvent> marketEvent = new ArrayList<MarketEvent>();

	public SimpleMarket(String nom, double pOPULATION, double sALAIRES,
			double iMPOTS, double cHOMAGE, double ePARGNE, double pRIX_TERRAIN) {
		super();

		this.nom = nom;
		this.POPULATION = pOPULATION;
		this.SALAIRES = sALAIRES;
		this.IMPOTS = iMPOTS;
		this.CHOMAGE = cHOMAGE;
		this.EPARGNE = ePARGNE;
		this.PRIX_TERRAIN = pRIX_TERRAIN;

		this.PIB = POPULATION * (1 - CHOMAGE) * SALAIRES;
		this.CONSOMMATION = PIB * (1 - IMPOTS) * (1 - EPARGNE);
		this.INVESTISSEMENTS = PIB * (1 - IMPOTS) - CONSOMMATION;

	}

	

	private void calculerValeursRefMarket(ReferenceMarket refM) {

		// On calcul le cout en matiere premiere en fonction des enfants si il
		// y'en as
		if (!refM.getMatieresPremieres().isEmpty()) {
			refM.coutMatiereParItem = 0;
			for (String refMatPrem : refM.getMatieresPremieres().keySet()) {
				if (!references.get(refMatPrem).isActualised) {
					calculerValeursRefMarket(references.get(refMatPrem));
				}
				refM.coutMatiereParItem += references.get(refMatPrem).prix
						* refM.getMatieresPremieres().get(refMatPrem);
			}
		}
		// idem pour infrastructures
		if (!refM.getInfrastructures().isEmpty()) {
			refM.besoinInfra = 0;
			for (String refInfra : refM.getInfrastructures().keySet()) {
				if (!references.get(refInfra).isActualised) {
					calculerValeursRefMarket(references.get(refInfra));
				}
				refM.besoinInfra += this.references.get(refInfra).prix
						* refM.getInfrastructures().get(refInfra);
			}
		}
		// TODO verif nico

		if (refM.technologie >= 2) {
			refM.BESOIN_CAPITAL = refM.besoinInfra
					/ Math.log10(refM.technologie);
		} else {
			refM.BESOIN_CAPITAL = refM.besoinInfra;
		}
		refM.ECONOMIE_ECHELLE = Math.log10(refM.capital) / 100;
		refM.CONSOMMATION = (1 - this.EPARGNE) * PIB * refM.partMarché;
		refM.DEMANDE = refM.CONSOMMATION / refM.prix;
		refM.CAPA_PRODUCTION = refM.DEMANDE * refM.BESOIN_CAPITAL
				* (1 - refM.ECONOMIE_ECHELLE);
		refM.COUT_INFRA = refM.CAPA_PRODUCTION / refM.DEMANDE;
		refM.OFFRE = refM.capital / refM.COUT_INFRA;
		refM.NBR_TRAVAILLEUR = refM.OFFRE / refM.production;
		refM.MASSE_SALARIALE = refM.NBR_TRAVAILLEUR * this.SALAIRES;

		refM.COUT_MATIERE = refM.coutMatiereParItem * refM.OFFRE;
		refM.INFRA_CIBLE = refM.BESOIN_CAPITAL * refM.DEMANDE;
		refM.REMK = refM.CAPA_PRODUCTION / refM.capital * this.TAUX_INTERETS;
		refM.COUT_PROD = (refM.COUT_MATIERE + refM.MASSE_SALARIALE + refM.capital / 100)
				/ refM.OFFRE;
		refM.INVESTISSEMENT = Math.max(0, Math.min(0.06 * refM.capital,
				(refM.REMK + 0.002 - this.TAUX_INTERETS) * 5
						* refM.CAPA_PRODUCTION));
		refM.INVESTISSEMENT_TECHNO = Math.max(0, Math.min(
				0.06 * refM.CAPA_PRODUCTION,
				(refM.REMK + 0.002 - this.TAUX_INTERETS) * 5
						* refM.CAPA_PRODUCTION));

		refM.capital = ((refM.capital * 0.99) + refM.INVESTISSEMENT);
		refM.technologie += refM.INVESTISSEMENT_TECHNO;

		refM.prix = Math
				.max(0.85,
						Math.min(
								1.15,
								(((Math.max(0, (refM.DEMANDE / refM.OFFRE) - 1) / 4) + 1) * refM.COUT_PROD)
										/ refM.prix))
				* refM.prix * (1 + refM.REMK + 0.05);
		refM.previousPrices.add(refM.prix);

		refM.isActualised = true;
	}

	private void calculerINFRASTRUCTURES() {
		this.INFRASTRUCTURES = 0;
		for (ReferenceMarket ref : references.values()) {
			this.INFRASTRUCTURES += ref.prix;
		}
	}

	private void calculerTAUXINTERET() {
		this.TAUX_INTERETS = 0.05;
	}


	public void setReferenceMarket(Marketable marketable,
			ReferenceMarket referenceMarket) {

		if (!references.containsKey(marketable)) {
			references.put(marketable.getReferenceMarket(),
					referenceMarket);
			this.NombreMarketable++;
		}
		calculerINFRASTRUCTURES();
		calculerTAUXINTERET();

	}

	
	public void supprimerValeurSurMarket(Marketable marketable) {

		if (null != this.references.remove(marketable.getReferenceMarket())) {

			this.NombreMarketable--;

			calculerINFRASTRUCTURES();
			calculerTAUXINTERET();
		}

	}

	
	public void simulerLeMarché() {

		for (ReferenceMarket ref : this.references.values()) {

			if (!ref.isActualised)
				calculerValeursRefMarket(ref);

		}

		for (ReferenceMarket ref : this.references.values()) {
			ref.isActualised = false;
		}

	}


	public int simulerVenteSurMarché(Marketable marketable, int quantité,
			double prixDésiré) {

		return 0;
	}

	
	public void modifierValeur(Marketable marketable, int difference) {
		ReferenceMarket referenceMarket = this.references.get(marketable
				.getReferenceMarket());
		referenceMarket.prix += difference;
		referenceMarket.isActualised = true;
		// TODO
	}

	
	public double recupererValeur(Marketable marketable) {
		return this.references.get(marketable.getReferenceMarket()).prix;
	}

	public HashMap<String, ReferenceMarket> getReferences() {
		return references;
	}

	public double modifierSalaires(double difference) {
		this.SALAIRES += difference;
		return this.SALAIRES;
	}

	public double modifierTauxChomage(double difference) {
		this.CHOMAGE += difference;
		return this.CHOMAGE;
	}

	public double modifierTauxInteretsBanquaires(double difference) {
		this.TAUX_INTERETS += difference;
		return this.TAUX_INTERETS;
	}

	public void simulerLeMarché(int iteration) {
		for (int i = 0; i < iteration; i++) {
			this.simulerLeMarché();
		}

	}

	public void clearCachedData() {
		for (ReferenceMarket reference : this.references.values()) {
			reference.previousPrices.clear();
		}

	}

	public void addEvent(MarketEvent marketEvent) {
		this.marketEvent.add(marketEvent);

	}

	public void simulateMarketWithEvent() {
		for (MarketEvent event : this.marketEvent) {
			event.affectMarket(this);
		}
		this.simulerLeMarché();
	}

	public void simulateMarketWithEvent(int iteration) {
		for (int i = 0; i < iteration; i++) {
			this.simulateMarketWithEvent();
		}

	}


	public List<MarketEvent> getAllEvents() {
		return this.marketEvent;
	}

	public void removeAllEvents() {
		this.marketEvent.clear();
	}

}
